package com.gitee.wsl.lang.bytearray


import com.gitee.wsl.ext.base.Arrays.arraycopy
import com.gitee.wsl.io.buffer.ByteBuffer
import kotlin.jvm.JvmOverloads
import kotlin.math.min

// copy from org.bouncycastle.tls.ByteQueue
/**
 * A queue for bytes. This file could be more optimized.
 */
class ByteQueue {
    /**
     * The buffer where we store our data.
     */
    private var databuf: ByteArray

    /**
     * How many bytes at the beginning of the buffer are skipped.
     */
    private var skipped = 0

    /**
     * How many bytes in the buffer are valid data.
     */
    private var available = 0

    private var readOnlyBuf = false

    @JvmOverloads
    constructor(capacity: Int = 0) {
        databuf = if (capacity == 0) EMPTY_BYTES else ByteArray(capacity)
    }

    constructor(buf: ByteArray, off: Int, len: Int) {
        this.databuf = buf
        this.skipped = off
        this.available = len
        this.readOnlyBuf = true
    }

    /**
     * Add some data to our buffer.
     *
     * @param buf A byte-array to read data from.
     * @param off How many bytes to skip at the beginning of the array.
     * @param len How many bytes to read from the array.
     */
    fun addData(buf: ByteArray, off: Int, len: Int) {
        check(!readOnlyBuf) { "Cannot add data to read-only buffer" }

        if (available == 0) {
            if (len > databuf.size) {
                val desiredSize = nextTwoPow(len or 256)
                databuf = ByteArray(desiredSize)
            }
            skipped = 0
        } else if ((skipped + available + len) > databuf.size) {
            val desiredSize = nextTwoPow(available + len)
            if (desiredSize > databuf.size) {
                val tmp = ByteArray(desiredSize)
                arraycopy(databuf, skipped, tmp, 0, available)
                databuf = tmp
            } else {
                arraycopy(databuf, skipped, databuf, 0, available)
            }
            skipped = 0
        }

        arraycopy(buf, off, databuf, skipped + available, len)
        available += len
    }

    /**
     * @return The number of bytes which are available in this buffer.
     */
    fun available(): Int {
        return available
    }

    /**
     * Copy some bytes from the beginning of the data to the provided [OutputStream].
     *
     * @param output The [OutputStream] to copy the bytes to.
     * @param length How many bytes to copy.
     */
//    @Throws(java.io.IOException::class)
//    fun copyTo(output: java.io.OutputStream, length: Int) {
//        check(length <= available) { "Cannot copy " + length + " bytes, only got " + available }
//
//        output.write(databuf, skipped, length)
//    }

    /**
     * Read data from the buffer.
     *
     * @param buf    The buffer where the read data will be copied to.
     * @param offset How many bytes to skip at the beginning of buf.
     * @param len    How many bytes to read at all.
     * @param skip   How many bytes from our data to skip.
     */
    fun read(buf: ByteArray, offset: Int, len: Int, skip: Int) {
        require((buf.size - offset) >= len) {
            ("Buffer size of " + buf.size
                    + " is too small for a read of " + len + " bytes")
        }
        check((available - skip) >= len) { "Not enough data to read" }
        arraycopy(databuf, skipped + skip, buf, offset, len)
    }

    /**
     * Read data from the buffer.
     *
     * @param buf  The [ByteBuffer] where the read data will be copied to.
     * @param len  How many bytes to read at all.
     * @param skip How many bytes from our data to skip.
     */
    fun read(buf: ByteBuffer, len: Int, skip: Int) {
        val remaining: Int = buf.remaining
        require(remaining >= len) { "Buffer size of $remaining is too small for a read of $len bytes" }
        check((available - skip) >= len) { "Not enough data to read" }
        buf.putBytes(databuf, skipped + skip, len)
    }

    /**
     * Remove some bytes from our data from the beginning.
     *
     * @param i How many bytes to remove.
     */
    fun removeData(i: Int) {
        check(i <= available) { "Cannot remove $i bytes, only got $available" }

        /*
         * Skip the data.
         */
        available -= i
        skipped += i
    }

    /**
     * Remove data from the buffer.
     *
     * @param buf  The buffer where the removed data will be copied to.
     * @param off  How many bytes to skip at the beginning of buf.
     * @param len  How many bytes to read at all.
     * @param skip How many bytes from our data to skip.
     */
    fun removeData(buf: ByteArray, off: Int, len: Int, skip: Int) {
        read(buf, off, len, skip)
        removeData(skip + len)
    }

    /**
     * Remove data from the buffer.
     *
     * @param buf  The [ByteBuffer] where the removed data will be copied to.
     * @param len  How many bytes to read at all.
     * @param skip How many bytes from our data to skip.
     */
    fun removeData(buf: ByteBuffer, len: Int, skip: Int) {
        read(buf, len, skip)
        removeData(skip + len)
    }

    fun removeData(len: Int, skip: Int): ByteArray {
        val buf = ByteArray(len)
        removeData(buf, 0, len, skip)
        return buf
    }

    fun shrink() {
        if (available == 0) {
            databuf = EMPTY_BYTES
            skipped = 0
        } else {
            val desiredSize = nextTwoPow(available)
            if (desiredSize < databuf.size) {
                val tmp = ByteArray(desiredSize)
                arraycopy(databuf, skipped, tmp, 0, available)
                databuf = tmp
                skipped = 0
            }
        }
    }

    fun write(b: Int) {
        addData(byteArrayOf(b.toByte()), 0, 1)
    }

    fun write(b: ByteArray, off: Int, len: Int) {
        addData(b, off, len)
    }

    fun write(b: ByteArray?) {
        write(b!!, 0, b.size)
    }

    fun clear() {
        available = 0
        skipped = 0
    }

    fun peek(buf: ByteArray): Int {
        val bytesToRead = min(available(), buf.size)
        read(buf, 0, bytesToRead, 0)
        return bytesToRead
    }

    fun read(): Int {
        if (available() == 0) {
            return -1
        }
        return removeData(1, 0)[0].toInt() and 0xFF
    }

    @JvmOverloads
    fun read(b: ByteArray, off: Int = 0, len: Int = b.size): Int {
        val bytesToRead = min(available(), len)
        removeData(b, off, bytesToRead, 0)
        return bytesToRead
    }

    fun skip(n: Long): Long {
        val bytesToRemove = min(n.toInt(), available())
        removeData(bytesToRemove)
        return bytesToRemove.toLong()
    }


//    fun readToString(charset: java.nio.charset.Charset?): String {
//        val sb: java.lang.StringBuilder = java.lang.StringBuilder()
//        val `in`: ByteQueueInputStream = ByteQueueInputStream(this)
//        val reader: java.io.InputStreamReader = java.io.InputStreamReader(`in`, charset)
//        try {
//            do {
//                val c: Int = reader.read()
//                if (c < 0) break
//                sb.append(c.toChar())
//            } while (true)
//        } catch (e: java.io.IOException) { // NOPMD
//            // ignore error
//        } finally {
//            IoHelper.safeCloseObject(reader)
//            IoHelper.safeCloseObject(`in`)
//        }
//        return sb.toString()
//    }

    companion object {
        val EMPTY_BYTES: ByteArray = ByteArray(0)

        /**
         * @return The smallest number which can be written as 2^x which is bigger than i.
         */
        fun nextTwoPow(i: Int): Int {
            /*
         * This code is based of a lot of code I found on the Internet which mostly
         * referenced a book called "Hacking delight".
         */
            var i = i
            i = i or (i shr 1)
            i = i or (i shr 2)
            i = i or (i shr 4)
            i = i or (i shr 8)
            i = i or (i shr 16)
            return i + 1
        }
    }
}